#include <fstream>
#include <sstream>
#include <vector>
#include <stdexcept>
#include <deque>
#include <string>
#include <iostream>
#include <map>

//#include <unistd.h>
//#include <sys/types.h>
//#include <sys/stat.h>

#include "boost/tuple/tuple.hpp"

#define DEBUG_LEVEL 6
#include <debug_func.h>

#include <blast_results.h>
#include <index_string.h>
#include <portable_timer.h>
#include <codeml_results.h>
#include <blast_results.h>
#include <progress_indicator.h>
#define _MY_STL_MACROS
#include <stlhelper.h>
#include <merge_unique.h>
#include <ultoa.h>
#include <print_error.h>
#include <open_stream_name_with_substitutions.h>
#include <open_fstream.h>
#include <md4.h>
#include <digest.h>
#include <seq_pairs.h>
#include <get_filename.h>

#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/foreach.hpp>


#include "get_arg.h"


using std::vector;
using std::pair;
using std::deque;
using std::ofstream;
using std::ostream;
using std::ifstream;
using std::istream;
using std::istringstream;
using std::string;
using std::map;

using std::cerr;
using std::cin;
using std::cout;
using boost::tie;
using bcpp_align::t_align_results;

struct t_counts
{
	// errors and redundant
	unsigned cnt_duplicates;
	unsigned cnt_identical;
	unsigned cnt_ignored;

	// what is output
	unsigned cnt_unique;	// total
	unsigned cnt_unchanged;
	unsigned cnt_swapped;
	t_counts():
		cnt_duplicates(0),
		cnt_identical(0),
		cnt_ignored(0),
		cnt_unique(0),
		cnt_unchanged(0),
		cnt_swapped(0){}
};


//________________________________________________________________________________________

//		t_digest_used

//________________________________________________________________________________________
struct t_digest_used
	: public t_digest
{
	unsigned	used;
	double		e_value;
	t_digest_used(double e_value_):used(1), e_value(e_value_){}
	bool operator ==(const t_digest_used& o) const ;
	bool operator <(const t_digest_used& o) const ;
};
inline bool t_digest_used::operator ==(const t_digest_used& o) const
{
	return static_cast<const t_digest&>(*this) == static_cast<const t_digest&>(o);
}


//
//	sort by digest then e_value
//
inline bool t_digest_used::operator <(const t_digest_used& o) const
{
	if (static_cast<const t_digest&>(*this) == static_cast<const t_digest&>(o))
		return e_value < o.e_value;
	else
		return static_cast<const t_digest&>(*this) < static_cast<const t_digest&>(o);
}


struct op_merge_digest_counts
{
	void operator()(t_digest_used& a, const t_digest_used b)
	{
		a.used += b.used;
	}
};

















//________________________________________________________________________________________

//		do_record_pairs

//________________________________________________________________________________________
struct do_record_pairs
{
	deque<t_digest_used>&				id_digests;
	t_program_args&						args;
	std::string							comment_letters;
	do_record_pairs(deque<t_digest_used>&	id_digests_,	
					t_program_args&			args_,
					const std::string&		comment_letters_):
			id_digests(id_digests_),
			args(args_),
			comment_letters(comment_letters_){}
	void operator()(istream& is, unsigned cnt_line)
	{
		// get data for one protein pair
		string name1, name2;

		// comment if false
		string comment_line;
		if (!seq_pairs::parse_ids(is, name1, name2, comment_line, comment_letters,
							  seq_pairs::ePARSE_IDS_ONLY))
			return;


		//
		//	discard identical identifiers
		//
		if (name1 == name2 && args.discard_identical)
		{
			is.ignore(32768, '\n');
			is.putback('\n');
			return;
		}


		
		if (name1 > name2)
			name1.swap(name2);

		string ids = name1 + '\t' + name2;

		



		if (args.mode == eIDs)
		{
			// calculate and save hash
			id_digests.push_back(t_digest_used(0.0));
			md4_hash(ids, id_digests.back());

			// continue
			is.ignore(32768, '\n');
			is.putback('\n');
			return;
		}

		t_codeml_results dndsdata;
		if (args.mode == eKAKS)
			is >> dndsdata;

		t_align_results aligndata;
		is >> aligndata;

		if (!is)
			throw std::runtime_error("Invalid data");

		// calculate and save hash
		id_digests.push_back(t_digest_used(aligndata.e__value));
		md4_hash(ids, id_digests.back());
	}
};


























struct do_write_pairs
{
	t_counts&							cnts;
	deque<t_digest_used>&				id_digests;
	t_program_args&						args;
	std::string							comment_letters;
	do_write_pairs(	t_counts&				cnts_,
					deque<t_digest_used>&	id_digests_,	
					t_program_args&			args_,
					const std::string&		comment_letters_):

			cnts(cnts_), 
			id_digests(id_digests_),
			args(args_),
			comment_letters(comment_letters_)
			{}


	void operator()(istream& is, unsigned cnt_line)
	{
		// get data for one protein pair
		string name1, name2;

		// comment if false
		string comment_line;
		if (!seq_pairs::parse_ids(is, name1, name2, comment_line, comment_letters,
							  seq_pairs::ePARSE_IDS_ONLY))
		{						  
			cout	<< comment_line;
			return;
		}		


		//
		//	discard identical identifiers
		//
		if (name1 == name2 && args.discard_identical)
		{
			is.ignore(32768, '\n');
			is.putback('\n');
			++cnts.cnt_identical;
			return;
		}

		
		bool do_swap = false;
		if (name1 > name2)
		{
			name1.swap(name2);
			do_swap = true;
		}

		string ids = name1 + '\t' + name2;

		// calculate and find hash
		t_digest_used hash_value (0.0);
		md4_hash(ids, hash_value);

		typedef deque<t_digest_used>::iterator ITER;
		ITER iter = lower_bound(id_digests.begin(), id_digests.end(), hash_value);
		assert(iter != id_digests.end());
		if (!(*iter == hash_value))
		{
			std::cerr << "different!!!!\n";
			return;
		}

		
		// norearrangement necessary
		if (args.mode == eIDs)
		{
			string line;
			getline(is, line);
			is.putback('\n');
			if (iter->used)
			{
				++cnts.cnt_duplicates;
				return;
			}
			++iter->used;
			cout << name1 << "\t" << name2 << line << "\n";
			if (do_swap)
				++cnts.cnt_swapped;
			else
				++cnts.cnt_unchanged;
			return;
		}

		
		// read dnds and align data
		t_codeml_results dndsdata;
		if (args.mode == eKAKS)
			is >> dndsdata;

		t_align_results aligndata;
		is >> aligndata;

		if (!is)
			throw std::runtime_error("Invalid data");

		// ignore if different e_value or already printed out
		if (iter->used)
		{
			++cnts.cnt_duplicates;
			return;
		}
		if (iter->e_value != aligndata.e__value)
		{
			++cnts.cnt_ignored;
			return;
		}

		if (do_swap)
			aligndata.swap_subject_query();

		cout << name1 << "\t" << name2 << "\t";
		if (args.mode == eKAKS)
			cout << dndsdata << "\t";
		cout << aligndata << "\n";
		if (do_swap)
			++cnts.cnt_swapped;
		else
			++cnts.cnt_unchanged;
		iter->used = 1;
	}
};

typedef map<unsigned,  unsigned> t_map_uu;
typedef t_map_uu::iterator t_map_uu_iter;
void summarise_counts(const deque<t_digest_used>& id_digests, t_map_uu& counts)
{
	BOOST_FOREACH(const t_digest_used& du, id_digests) 
	{
		++counts[du.used];
	}
}




//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//		Main

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
inline void print_helper(std::ostream& VERBOSE, unsigned value, const string& desc)
{
	if (value)
		VERBOSE << "\t" << std::setw(8) << value << "\t" << desc << "\n";
}

void write_counts_summary_to_stream(std::ostream&	strm_summary, 
									const t_counts&	cnts, 
									const t_map_uu&	count_duplicates)
{
	strm_summary << "\t" << "Of the identifier pairs printed:\n";
	print_helper(strm_summary, cnts.cnt_unchanged  ,
				 "were in alphabetical order already."            					);
	print_helper(strm_summary, cnts.cnt_swapped    ,              					
				 "were swapped around.\n"                         					);
	print_helper(strm_summary, cnts.cnt_unique     ,
				 "identifier pairs printed altogether. "			  	            );
	print_helper(strm_summary, cnts.cnt_identical  ,
				 "lines discarded where both identifiers in a pair were identical." );
	print_helper(strm_summary, cnts.cnt_duplicates + cnts.cnt_ignored ,
				 "lines with duplicate identifier pairs ignored."                   );
	strm_summary << "\t" << string (8, '-') << "\t" << string (20, '-') << "\n";

	strm_summary << "\t" << std::setw(8) << (cnts.cnt_duplicates + 
												 cnts.cnt_ignored + 
												 cnts.cnt_swapped + 
												 cnts.cnt_identical +
												 cnts.cnt_unchanged)
								<< "\tlines in total.\n\n"; 
	
	std::pair<int,int> pair_cnt;
	BOOST_FOREACH(pair_cnt, count_duplicates) 
		strm_summary	<< "\t" << std::setw(8) << pair_cnt.second
						<< "\tidentifier pairs were each found"  << std::setw(10)
						<< pair_cnt.first << " times\n";
	strm_summary << std::endl;
	
}					

int main(int argc, char* argv[])
{
	debug_func_cerr(5);
	t_program_args args;

	try
	{
	
		// don't use C streams
		std::ios::sync_with_stdio(false);
	
	
		// get command line arguments
		if (!process_command_line_options(argc, argv, args))
			return 1;
	
		string file_name = get_filename(args.file_input);
		args.VERBOSE() << "\tRecording sequence pairs from [" 
							<< file_name << "]...\n";



		//
		// record digests for sequence pairs
		//
		deque<t_digest_used> id_digests;
		{
			do_record_pairs parse_pairs(id_digests, args, args.comment_letters);
			seq_pairs::read_data(parse_pairs, args.file_input, args.VERBOSE());
		}


		//
		// sort by e_values
		//
		args.VERBOSE() << "\tSorting ids by e-values...\n";
		sort(id_digests.begin(), id_digests.end());

		//
		// only keep lowest e_value
		//
		args.VERBOSE() << "\tMerging duplicate ids...\n";
		merge_unique(id_digests, std::equal_to<t_digest>(), op_merge_digest_counts());

		t_map_uu count_duplicates;
		summarise_counts(id_digests, count_duplicates);

		BOOST_FOREACH(t_digest_used& du, id_digests) 
			du.used = 0;

		//
		// write back out
		//
		t_counts cnts;
		cnts.cnt_unique = id_digests.size();
		do_write_pairs parse_pairs(cnts, id_digests, args, args.comment_letters);
		seq_pairs::read_data(parse_pairs, args.file_input, args.VERBOSE());
		


		//
		// printout summary
		//
		write_counts_summary_to_stream(args.VERBOSE(), cnts, count_duplicates);
		write_counts_summary_to_stream(args.SUMMARY(), cnts, count_duplicates);
		


	}
	catch (std::exception& e)
	{
		std_print_error(args.CERR(), e.what());
		return 1;
	}

	return 0;
}

